Сообщений 8    Оценка 55 [+0/-1]         Оценить  
Система Orphus

Так всё-таки, что же такое Inversion of Control?

Автор: Бучельников Игорь Владимирович
Опубликовано: 30.11.2012
Исправлено: 10.12.2016
Версия текста: 1.0
Вступление
Сокращения
IoC
IoCContainer
Терминология
Компонент
Система
Зависимость компонентов
Разделяемый компонент
Точка входа
Подсистема
Надсистема
Суперсистема
Подкомпонент
Поток управления (Control)
Аспект масштабирования системы
Соглашения
Обозначения
UML диаграммы классов и листинги программного кода
Теоретическая часть
Рабочий пример
Прямой поток управления
Инверсия потока управления
Когда без IoC не обойтись
Является ли событийная модель синонимом IoC?
Библиотеки и каркасы
Техническая реализация IoC
Шаблон проектирования «Factory Method»
Inversion of Control Сontainer
Dependency Injection
Заключение

Вступление

В очередной раз, поймав себя на мысли, что я не понимаю, что такое Inversion of Control, я решил разобраться. Техническая реализация (Inversion of Control Container и шаблон проектирования «Factory Method») была понятна. Я даже сделал свою реализацию Inversion of Control Container и успешно её использовал в реальных проектах. Но абстрактная теоретическая основа оставалась не постигнутой. А именно хотелось получить ответы на следующие вопросы:

  1. Почему происходит уменьшение связанности при использовании Inversion of Control?
  2. Какова область применения Inversion of Control?
  3. Бывают ли случаи, когда без Inversion of Control не обойтись?
  4. Является ли событийная модель синонимом принципа Inversion of Control?
  5. Чем отличается библиотека (library) от каркаса (framework)?
  6. Можно ли следовать принципу Inversion of Control и при этом не использовать Factory Method, Inversion of Control Container и Dependency Injection?

Статьи, найденные в интернете, в основном описывали технику реализации, которая и так была понятна. Теоретические описания даны недостаточно формально и (или) дают определение Inversion of Control не напрямую, как фундаментальное понятие, а косвенным образом через другие известные концепции. Встречаются таинственные заявления типа:

Единственную достаточно качественную попутку формализовать Inversion of Control я нашел в этой статье. Но в ней, на мой взгляд, формализуется событийная модель, а не Inversion of Control.

Сокращения

IoC

Inversion of Control ― инверсия потока управления

IoCContainer

Inversion of Control Container

Терминология

Компонент

Контейнер для методов или для других компонентов. В Visual Studio это могут быть класс(ы) или проект(ы).

Система

Множество связанных компонентов. Связь здесь означает, что компонент А зависит от компонента Б.

Зависимость компонентов

Предположим, что компонент А зависит от компонента Б. В этом случае А не может работать без Б. Но Б может работать без А. Метод А может вызвать метод Б, но метод Б не может вызвать метод А. В Visual Studio зависимостью двух компонентов может являться ссылка одного проекта на другой.

Разделяемый компонент

Компонент, от которого зависит более одного компонента. Чем больше разделяемых компонентов, и чем больше компонентов от них зависит, тем выше связанностьсистемы.

ПРИМЕЧАНИЕ

Высокая связанность системы увеличивает суммарный межкомпонентный интерфейс системы (сумму межкомпонентных интерфейсов всех компонентов). Межкомпонентный интерфейс — это обязательства (контракт (протокол), по которому взаимодействуют компоненты), которые должны соблюдать программисты, реализующие компоненты. А чем меньше этих обязательств, тем меньше трудозатрат требуется на их обеспечение, и тем меньше трудозатрат потребуется в случае изменения этих обязательств при изменении требований к системе.

Точка входа

Метод компонента системы (подсистемы), при вызове которого система (подсистема) выполняет возложенные на неё функции (этот метод вызывает по цепочке все остальные методы всех компонентов системы (подсистемы), в свою очередь, этот метод ни один другой методсистемы (подсистемы) не вызывает).

Подсистема

Подмножество связанных компонентов системы, имеющее точку входа.

Надсистема

Система, которая является родительской по отношению ко всем своим подсистемам.

Суперсистема

Система, которая не является подсистемой какой-либо другой системы.

Подкомпонент

Компонент, который содержится в другом компоненте. Подкомпоненты образуют подсистему. Подкомпоненты полностью изолированы от всех других компонентов. Изолированность означает то, что ни один из подкомпонентов не может зависеть ни от одного из других внешних компонентов, и ни один из других внешних компонентов не может зависеть ни от одного из подкомпонентов.

Поток управления (Control)

Последовательность вызова одних методов другими. Если не принимать во внимание событийную модель, поток управления всегда направлен от точки входа.

Аспект масштабирования системы

Путь (способ), по которому может наращиваться функциональность системы или подсистемы.

Соглашения

Обозначения

Термины и сокращения, которые приведены в разделах Сокращения и Терминология, а также те, на которые следует обратить внимание, выделены курсивом. Названия компонентов выделены жирным.

UML диаграммы классов и листинги программного кода

На всех UML-диаграммах классов в данном документе вместо классов будут изображены компоненты. Листинги программного кода будут представлены на языке C#. В них класс с именем Name является фасадным классом (смотрите паттерн «Фасад») для компонента с именем Name на диаграмме UML.

Листинги программного кода не являются образцом совершенства. Некоторые несущественные технические детали могут быть опущены в целях акцентирования внимания на сути. Код можно считать C#-образным псевдокодом.

Абстрактный компонент — это компонент, у которого фасадный класс является абстрактным. Названия абстрактных компонентов помечены курсивом. На всех UML-диаграммах абстрактный компонент при необходимости может быть заменен интерфейсом или неабстрактным компонентом. Абстрактный компонент выбран просто для примера и не влияет на суть изложенного материала.

Теоретическая часть

Рабочий пример

Для примера будем рассматривать программу для печати графиков биржевых индексов РТС и ММВБ:


Рисунок 1


Рисунок 2

Графики очень похожи. Отличаются только данные и легенда. Предположим, перед нами стоит задача получить на выходе два ярлыка на рабочем столе, при запуске первого, без лишних вопросов пользователю, на принтер отправляется график ММВБ за сессию прошлого дня, при запуске второго то же самое происходит с графиком РТС. У этой системы должно быть предусмотрено 2 аспекта масштабирования:

  1. Добавление новых биржевых индексов.
  2. Изменение визуального стиля графиков (цветной, черно-белый).

Прямой поток управления

Вот как будет выглядеть архитектура данной системы без применения принципа IoC, то есть direct control. В данной архитектуре будет предусмотрен только первыйаспект масштабирования.


Рисунок 3.

Точки входа находятся в компонентах Принтер_ММВБ и Принтер_РТС. На выходе эти компоненты будут давать exe-файлы, которые нам и требуются. Компонент График соответствует визуальному представлению графика биржевого индекса. Точки входа – это методы Main(args : string[]). Скорее всего, компонент Принтер_ММВБ и Принтер_РТС будут иметь общую логику, поэтому вводим компонент Принтер, наследниками которого они будут.

Опишу, как работает данная архитектура. Для примера будем рассматривать компонент Принтер_ММВБ. Вот то, что находится в Принтер_ММВБ.Main(args : string[]):

Main(string[] args)
{
  Принтер_ММВБ принтер_ММВБ = new Принтер_ММВБ();
  ДанныеГрафика данныеГрафика = принтер_ММВБ.ПолучитьДанные();
  ГенераторГрафиков генераторГрафиков = new ГенераторГрафиков();
  График график = генераторГрафиков.ПолучитьГрафик(данныеГрафика, "ММВБ");
  график.Напечатать();
}

Теперь рассмотрим первыйаспект масштабирования. Добавление нового биржевого индекса ИНД потребует добавления нового компонента Принтер_ИНД. Все остальные компоненты системы должны остаться без изменений. Таким образом, в контексте первогоаспекта масштабированиясистему можно разделить на динамические и статические компоненты. В контексте первогоаспекта масштабированиядинамическими компонентами являются Принтер_ММВБ и Принтер_РТС. Статическими компонентами являются все остальные компоненты: ДанныеГрафика, ГенераторГрафиков, График и Принтер. Поток управления направлен от динамических компонентов к статическим. Это и есть определение прямого потока управления.

Инверсия потока управления

Вот как будет выглядеть архитектура данной системы после инверсии прямогопотока управления. По-прежнему в данной архитектуре будет предусмотрен только первыйаспект масштабирования.


Рисунок 4

Первое, что бросается в глаза ― это то, что стрелочек зависимостей стало меньше. Уменьшилось количество разделяемых компонентов: в архитектуре direct control это были компоненты ДанныеГрафика, ГенераторГрафиков и График, при использовании IoC только компонент ДанныеГрафика является разделяемым.

Теперь у нас одна точка входа: Принтер.Main(args : string). Теперь в параметре args (это аргументы командной строки) мы будем передавать название индекса («ММВБ» или «РТС»), который нужно напечатать.

Опишу, как работает данная архитектура. Вот то, что находится в Принтер.Main(args : string[]):

Main(string[] args)
{
  ПровайдерДанныхГрафика провайдерДанныхГрафика;
  if (args[0] == "ММВБ")
    провайдерДанныхГрафика = new ПровайдерДанныхГрафика_ММВБ();
  
  if (args[0] == "РТС")
    провайдерДанныхГрафика = new ПровайдерДанныхГрафика_РТС();
  
  ДанныеГрафика данныеГрафика = провайдерДанныхГрафика.ПолучитьДанные();
  ГенераторГрафиков генераторГрафиков = new ГенераторГрафиков();
  График график = генераторГрафиков.ПолучитьГрафик(данныеГрафика, args[0]);
  график.Напечатать();
}

Стрелки от компонента Принтер к компоненту ПровайдерДанныхГрафика_ММВБ и от компонента Принтер к компоненту ПровайдерДанныхГрафика_РТС сделаны нежирными неспроста. Они могут вообще отсутствовать (или даже направлены в обратную сторону, если очень нужно), если в технической реализации используется IoCContainer или паттерн «Factory Method». Получается, что связанность может уменьшаться не только благодаря самому принципу IoC, но и благодаря его особой технической реализации, но об этом будет сказано ниже. В приведенном выше псевдокоде IoCContainer и паттерн «Factory Method» не используются, поэтому стрелки присутствуют.

На диаграмме нет компонентовГенераторГрафиков и График, но в коде они присутствуют. Это потому, что с использованием IoC появилась возможность инкапсулировать эти компоненты в компонент Принтер, что я и сделал для упрощения структуры суперсистемы. То есть теперь компоненты ГенераторГрафиков и График стали подкомпонентами компонента Принтер, поэтому стали изолированы от компонентов свой надсистемы. В этом заключается упрощение и уменьшение связанностисистемы (но не только в этом).

А что, если бы нам понадобилось использовать компоненты ГенераторГрафиков и График в каких-нибудь других системах? Тогда пришлось бы отказаться от инкапсуляции этих компонентов в компонент Принтер, а архитектура выглядела бы следующем образом:


Рисунок 5

Замечу, что количество разделяемых компонентов не изменилось.

Теперь рассмотрим первыйаспект масштабирования. Добавление нового биржевого индекса ИНД потребует добавления нового компонента ПровайдерДанныхГрафика_ИНД. Все остальные компоненты системы должны остаться без изменений. В контексте первогоаспекта масштабированиядинамическими компонентами являются ПровайдерДанныхГрафика_ММВБ и ПровайдерДанныхГрафика_РТС. Статическими компонентами являются все остальные компоненты: ДанныеГрафика, Принтер (ГенераторГрафиков, График). Поток управления направлен от статических компонентов к динамическим. Это и есть определение инверсированного потока управления.

ПРИМЕЧАНИЕ

Из кода видно, что принцип IoC полагается на полиморфизм (один из трех принципов ООП).

Когда без IoC не обойтись

Вернёмся к архитектуре прямогопотока управления (рисунок 3). На нем предусмотрен только первыйаспект масштабирования. Изменим архитектуру так, чтобы был предусмотрен и второйаспект масштабирования. Предположим, что обстоятельства складываются так, что изменять визуальный стиль графиков можно только в компоненте ГенераторГрафиков (хотя логичней было бы это сделать в компоненте График). Вот как будет выглядеть архитектура:


Рисунок 6

Ограничусь печатью одного биржевого индекса: ММВБ (РТС можно легко добавить на диаграмму, только тогда она получится перегруженной). То, что отображено на диаграмме рисунка 6, является подсистемой печати графика ММВБ.

Опишу, как работает данная архитектура. Вот то что находится в Принтер_ММВБ.Main(args : string[]):

Main(string[] args)
{
  ПровайдерДанныхГрафика провайдерДанныхГрафика;
  
  if (args[0] == "ММВБ")
провайдерДанныхГрафика = new ПровайдерДанныхГрафика_ММВБ();
  if (args[0] == "РТС")
провайдерДанныхГрафика = new ПровайдерДанныхГрафика_РТС();
  
  
  ГенераторГрафиков генераторГрафиков;
  
  if (args[1] == "Цветной")
    генераторГрафиков = new ГенераторГрафиков_Цветной();
  
  if (args[1] == "ЧерноБелый")
    генераторГрафиков = new ГенераторГрафиков_ЧерноБелый();
  
  ДанныеГрафика данныеГрафика = провайдерДанныхГрафика.ПолучитьДанные();
  График график = генераторГрафиков.ПолучитьГрафик(данныеГрафика, args[0]);
  график.Напечатать();
}

Теперь проанализируем, что получится при добавлении нового визуального стиля отображения графиков. Для этого потребуется добавление нового «наследника» компонента ГенераторГрафиков. Все остальные компоненты подсистемы должны остаться без изменений. В контексте второгоаспекта масштабированиядинамическими компонентами являются ГенераторГрафиков_Цветной и ГенераторГрафиков_ЧерноБелый. Статическими компонентами являются все остальные компоненты: Принтер_ММВБ, ДанныеГрафика, График, ГенераторГрафиков. Поток управления направлен от статических компонентов к динамическим. По определению этот поток управления инверсированный. А другого варианта у нас и нет, так как направление потока управления задает надсистема.

Замечу, что компонент Принтер_ММВБПринтер_РТС) является динамическим в первомаспекте масштабирования и статическим во втором. И суперсистема в контексте первого аспекта масштабирования построена по принципу Direct Control, а в контексте второгоаспекта масштабирования её подсистема построена по принципу IoC. Замечу, что вопрос, по какому принципу построена суперсистема в контексте второгоаспекта масштабирования, некорректен, так как второйаспект масштабирования относится только к подсистеме печати графика конкретного биржевого индекса.

Стрелки от компонента Принтер_ММВБ к компоненту ГенераторГрафиков_Цветной и от компонента Принтер_ММВБ к компоненту ГенераторГрафиков_ЧерноБелый сделаны нежирными по той причине, по которой сделаны нежирные стрелки на диаграммах рисунков 4 и 5.

Вернёмся к архитектуре инверсированногопотока управления (рисунки 4 и 5). Поддержку второгоаспекта масштабирования можно добавить и в неё, аналогично тому, как это сделано в архитектуре прямогопотока исполнения, и мы также будем вынуждены применить IoC.

Является ли событийная модель синонимом IoC?

Не является. Как и в IoC, при использовании событий также происходит инверсия потока управления, но другая инверсия. В IoC и событийной модели отличается понятие прямоты потока управления. В контексте принципа IoC прямым потоком управления является поток управления от динамических компонентов к статическим. Динамические и статические компоненты определены в контексте какого-либо аспекта масштабирования. Понятия аспекта масштабирования, динамических и статических компонентов являются понятиями, применимыми во время проектирования (design time). События же позволяют инвертировать поток управления во время выполнения (run time). И им совершенно не важно, какой поток управления они инвертируют: от динамических компонентов к статическим или от статических к динамическим.

ПРИМЕЧАНИЕ

Событийная модель опирается на возможность передать адрес метода в переменной и вызвать этот метод через эту переменную.

Библиотеки и каркасы

Чем отличается библиотека (library) от каркаса (framework)? Библиотека — это статические компоненты при прямом потоке управления (direct control). Каркас — это статические компоненты при инверсированном потоке управления (IoC). Похожая мысль прослеживается в статье Designing Reusable Classes (Ralph E. Johnson & Brian Foote).

ПРИМЕЧАНИЕ

Здесь термин «library» отличается от одноименного термина в аббревиатуре DLL (Dynamic-Link Library)

Техническая реализация IoC

До этого момента я писал преимущественно о теоретической стороне вопроса. Далее опишу, как на практике реализуется принцип IoC. Принцип IoC реализуется с помощью шаблона проектирования «Factory Method» («Фабричный метод») и библиотек IoCСontainer. Опционально в IoCСontainer позволяет выполнять инъекцию зависимости (Dependency Injection). В программных кодах, приведенных выше, не используется ни одно из этих средств. Как уже отмечалось, использование этих средств позволяет ещё больше уменьшить связанность компонентов (на диаграммах рисунков 4, 5, 6 исчезнут нежирные стрелки зависимостей).

Шаблон проектирования «Factory Method»

Вот как будет выглядеть диаграмма рисунка 4 с использование Factory Method:


Рисунок 7

Здесь смело можно убрать нежирные стрелки. В компонент ПровайдерДанныхГрафика добавляется статический метод ПолучитьЭкземпляр(имяИндекса : string). Если этому методу в качестве параметра передать строку «ММВБ» он возвратит «экземпляр» компонента ПровайдерДанныхГрафика_ММВБ (аналогично для других биржевых индексов). Для этого компонент ПровайдерДанныхГрафика должен зависеть от своих наследников. Это нехорошо, но об этом чуть ниже.

Опишу на псевдокоде, как работает данная архитектура:

Main(string[] args)
{
  ПровайдерДанныхГрафика провайдерДанныхГрафика = 
    ПровайдерДанныхГрафика.ПолучитьЭкземпляр(args[0]);
  ГенераторГрафиков генераторГрафиков = new ГенераторГрафиков();
  ДанныеГрафика данныеГрафика = провайдерДанныхГрафика.ПолучитьДанные();
  График график = генераторГрафиков.ПолучитьГрафик(данныеГрафика, args[0]);
  график.Напечатать();
}

Напомню, что в параметре args (это аргументы командной строки) метода Принтер.Main(args : string[]) передаётся название индекса («ММВБ» или «РТС»), который нужно напечатать.

У данной реализации есть следующее преимущество по сравнению с реализацией архитектуры, изображенной на рисунке 4. Предположим, система реализована по диаграмме рисунка 4, и нам понадобилось создать систему для статистической обработки данных изменения биржевых индексов. Предположим, в этой системе будет компонент ОбработчикДанныхГрафика. Этот компонент получает данные изменения биржевого индекса у компонента ПровайдерДанныхГрафика_ММВБ или ПровайдерДанныхГрафика_РТС, то есть становится зависимым от этих компонентов. Но компоненты ПровайдерДанныхГрафика_ММВБ или ПровайдерДанныхГрафика_РТС уже зависят от компонента Принтер. Таким образом, компоненты ПровайдерДанныхГрафика_ММВБ и ПровайдерДанныхГрафика_РТС становятся разделяемымыми, что увеличивает связанность. Если мы будем использовать паттерн «Factory Method», то компоненты ОбработчикДанныхГрафика и Принтер будут зависеть только от компонента ПровайдерДанныхГрафика, и только он станет разделяемым.

Я упоминал, что нехорошо, когда базовый компонент зависит от своих «наследников». Причины этого такие:

  1. Получается перекрёстная ссылка между компонентами: «наследники» зависят от базового компонента по определению наследования, базовый компонент зависит от наследников своим фабричным методом (ПолучитьЭкземпляр(имяИндекса : string)). Но если логика фабричного метода достаточно простая и статичная, то зависимостью базового компонента от наследников можно приберечь.
  2. Логику получения «экземпляра» наследника базового компонента мы вынуждены делать статической, а статическая логика не позволяет использовать такие принципы ООП, как наследование и полиморфизм. Проще говоря, мы не сможем сделать фабрику фабрик и иметь несколько разновидностей базовой фабрики. Если предполагается только одна фабрика, то этим недостатком так же можно пренебречь.

Если мириться с этими недостатками не получается или программисту хочется потренироваться использовать усложненный вариант паттерна «Фабричный метод», то придётся ввести еще один компонент ФабрикаПровайдеровДанныхГрафика. Архитектура будет выглядеть так (чтобы не загромождать диаграмму я убрал ПровайдерДанныхГрафика_РТС):


Рисунок 8

Опишу, как работает данная архитектура:

Main(string[] args)
{
  ФабрикаПровайдеровДанныхГрафика фабрикаПровайдеровДанныхГрафика
     = new ФабрикаПровайдеровДанныхГрафика();
  ПровайдерДанныхГрафика провайдерДанныхГрафика 
     = фабрикаПровайдеровДанныхГрафика.ПолучитьЭкземпляр(args[0]);
  
  ГенераторГрафиков генераторГрафиков = new ГенераторГрафиков();
  ДанныеГрафика данныеГрафика = провайдерДанныхГрафика.ПолучитьДанные();
  График график = генераторГрафиков.ПолучитьГрафик(данныеГрафика, args[0]);
  график.Напечатать();
}

Замечу, что вариант паттерна «Factory Method» с компонентом ФабрикаПровайдеровДанныхГрафика имеет один недостаток: компонент ПровайдерДанныхГрафика стал разделяемым.

ПРИМЕЧАНИЕ

Скорее всего, нам будет нужен только один экземпляр фабрики, то есть необходимо использовать паттерн «Singleton». Но в данной архитектуре для упрощения это игнорируется.

Inversion of Control Сontainer

В этом и следующем разделе я опишу поведение упрощенного и усредненного IoCContainer`а, который не существует в реальности. Реальный IoCContainer вы можете скачать из интернета или написать самостоятельно.

IoCContainer — это повторно используемый компонент, который, грубо говоря, является универсальной фабрикой из паттерна «Factory Method». Для каждого базового класса этому контейнеру можно задать, экземпляр какого наследника нужно создавать (какой провайдер данных графика нужно использовать: ПровайдерДанныхГрафика_ММВБ или ПровайдерДанныхГрафика_РТС).

Код реализации архитектуры рисунка 4 с использованием IoCContainer можно переписать следующим образом (диаграмма остается той же):

Main(string[] args)
{
  if (args[0] == "ММВБ")
    IoCC.Map(
      typeof(ПровайдерДанныхГрафика),
      typeof(ПровайдерДанныхГрафика_ММВБ));

  if (args[0] == "РТС")
    IoCC.Map(
      typeof(ПровайдерДанныхГрафика),
      typeof(ПровайдерДанныхГрафика_РТС));

  ПровайдерДанныхГрафика провайдерДанныхГрафика;
  провайдерДанныхГрафика = IoCC.Get<ПровайдерДанныхГрафика>();
  ДанныеГрафика данныеГрафика = провайдерДанныхГрафика.ПолучитьДанные();
  ГенераторГрафиков генераторГрафиков = new ГенераторГрафиков();
  График график = генераторГрафиков.ПолучитьГрафик(данныеГрафика, args[0]);
  график.Напечатать();
}

В строке 4 мы указываем IoCContainer`у, что в качестве провайдера данных графика мы будем использовать ПровайдерДанныхГрафика_ММВБ. В этой фразе слова «будем использовать» означают, что IoC.Get<ПровайдерДанныхГрафика>() возвратит нам экземпляр ПровайдерДанныхГрафика_ММВБ. Для РТС всё аналогично. Теперь не только в методе Принтер.Main(args : string[]), но и всех остальных компонентах системы мы может получить нужный провайдер данных графика. Причем при отображении базового класса на класс-наследник IoCContainer`у можно указать, что экземпляр класса-наследника должен быть единственным (singleton).

Это все хорошо, но как избавиться от нежирных стрелочек зависимостей на диаграмме рисунка 4? Информацию о том, какой провайдер данных графика использовать, можно записать в конфигурационном файле приложения. Иными словами, то, что написано в строках 4 и 8 предыдущего листинга, можно прописать в конфигурационном файле. В этом случае в параметры командной строки нужно передавать путь к файлу конфигурации IoCContainer`а, а не имя биржевого индекса. Архитектура системы будет выглядеть так:


Рисунок 9

Предыдущий листинг можно переписать так:

Main(string[] args)
{
  IoCC.ПутьКФайлуКонфигурации = args[0];

  ПровайдерДанныхГрафика провайдерДанныхГрафика;
  провайдерДанныхГрафика = IoCС.Get<ПровайдерДанныхГрафика>();
  ДанныеГрафика данныеГрафика = провайдерДанныхГрафика.ПолучитьДанные();
  ГенераторГрафиков генераторГрафиков = new ГенераторГрафиков();
  График график = генераторГрафиков.ПолучитьГрафик(данныеГрафика, 
    провайдерДанныхГрафика.ИмяИндекса);
  график.Напечатать();
}

При использовании файла конфигурации IoCContainer`а появляются дополнительные бонусы, которые не относятся к реализации принципа IoC:

  1. Можно указывать конфигурационные параметры наследника класса ПровайдерДанныхГрафика. Например, таймаут получения данных. Причем для каждого наследника набор параметров может различаться. У класса ПровайдерДанныхГрафика_ММВБ можно объявить поле Таймаут (или параметр конструктора), а в конфигурационном файле просто указать имя этого поля и значение, которое в него нужно записать. Можно также записывать значения неэлементарных типов данных (списки, экземпляры других классов).
  2. Сторонний разработчик может написать свой наследник класса ПровайдерДанныхГрафика и задействовать его без перекомпиляции основного приложения: достаточно будет просто изменить конфигурацию IoCContainer`а.

Dependency Injection

Dependency Injection — это функция, которую может выполнять IoCContainer.

Предположим нам понадобиться создать систему для статистической обработки данных изменения биржевых индексов. Предположим, в этой системе будет класс ОбработчикДанныхГрафика. У класса ОбработчикДанныхГрафика будет поле типа ПровайдерДанныхГрафика. Возможно, будут и другие аналогичные поля (поля типов базовых классов, в которые необходимо записать экземпляр нужного наследника). Мы можем попросить IoCContainer записать в эти поля экземпляры требуемых классов наследников. В поле типа ПровайдерДанныхГрафика будет записан требуемый экземпляр наследника класса ПровайдерДанныхГрафика (см. предыдущий раздел):

ОбработчикДанныхГрафика обработчикДанныхГрафика;
обработчикДанныхГрафика = new ОбработчикДанныхГрафика();
IoCC.ВыполнитьИнъекциюВ(обработчикДанныхГрафика);

Предположим, у класса ОбработчикДанныхГрафика будут наследники ОбработчикДанныхГрафик_Точный и ОбработчикДанныхГрафик_Грубый. Информация о том, какой обработчик данных графика нужно использовать занесена в IoCContainer. В этом случае инстанцирование переменной обработчикДанныхГрафика будет происходить следующим образом:

ОбработчикДанныхГрафика обработчикДанныхГрафика;
обработчикДанныхГрафика = IoCC.Get<ОбработчикДанныхГрафика>();

В этом случае явно просить IoCContainer выполнить инъекцию в обработчикДанныхГрафика не нужно (не нужно вызывать IoCC.ВыполнитьИнъекциюВ(обработчикДанныхГрафика)), так как инъекция выполнится при вызове IoCC.Get<ОбработчикДанныхГрафика>(). Если у наследника класса ОбработчикДанныхГрафика будет параметр конструктора типа ПровайдерДанныхГрафика, то IoCContainer запишет в этот параметр экземпляр необходимого наследника.

ПРИМЕЧАНИЕ

В реализации IoCContainer используется рефлексия (reflection).

Заключение

В статье приведено почти формальное (точнее почти формализуемое) определение IoC. Полную формализацию произвести невозможно, так как понятие аспекта масштабирования не формализуемо.

Из статьи ясно, что для успешного применения принципа IoC необходимо четко понимать какие аспекты масштабирования должны быть предусмотрены. Также видна область применения IoC: снижение трудозатрат при адаптации системы к изменениям требований (что справедливо для многих других паттернов проектирования), как в рамках аспектов масштабирования, так и вне этих рамок (за счёт уменьшения связанности системы).

В статье механизм уменьшения связанности системы показан только на примере. Формальное доказательство того, что связанность системы будет всегда уменьшаться при применении принципа IoC, в статье отсутствует. Оставляю эту проблему для будущих исследователей. Для успешного практического применения IoC данное доказательство не требуется. Однако получение данного доказательства, возможно, позволит открыть другие принципы построения систем, позволяющие уменьшить их связанность.


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 8    Оценка 55 [+0/-1]         Оценить